package p26_ControllerAdvice之ModelAttribute;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ModelFactory;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.ServletRequestDataBinderFactory;
import p25_控制器方法执行流程._HandlerMethod;

import java.lang.reflect.Method;

/**
 * 上一章介绍了@ModelAttribute加在方参数上(不加这个注解的参数也会被解析，仅当参数为非String、Integer等单一类型)，会被某个视图解析器解析并将模型数据放入ModelAndViewContainer
 * 如果@ModelAttribute加在方法上会如何呢?
 * 通过上一章的控制器方法执行流程得知，处理器适配器初始化时会做很多工作，其中就包括收集@ModelAttribute方法返回值作为model数据
 * 本节知识点：
 *    处理器适配器初始化时，会解析所有并执行@ModelAttribute方法(ControllerAdvice类内部的方法)，并将返回值作为model数据
 *    @ModelAttribute方法可以存在于@ControllerAdvice类里，也可以存在于@Controller类里
 *    前者是全局的，后者是适用于当前控制器的，如果两个类里都有@ModelAttribute方法，优先使用当前控制器类的
 */
public class _ModelAttribute {
    public static void main(String[] args) throws Exception {
        /**
         * 下面演示RequestMappingHandlerAdapter初始化时，会解析所有加在方法上的@ModelAttribute，并将方法返回值作为模型数据放入模型容器
         */
        //一些常规操作
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
        adapter.setApplicationContext(context);
        ServletInvocableHandlerMethod handlerMethod = new ServletInvocableHandlerMethod(new WebConfig.MyController(),
                new WebConfig.MyController().getClass().getMethod("foo", WebConfig.Name.class));
        handlerMethod.setDataBinderFactory(new ServletRequestDataBinderFactory(null, null));
        handlerMethod.setParameterNameDiscoverer(new DefaultParameterNameDiscoverer());
        handlerMethod.setHandlerMethodArgumentResolvers(_HandlerMethod.getArgumentResolverComposite(context));

        //执行初始化操作，收集所有的@ModelAttribute方法
        adapter.afterPropertiesSet();
        Method getModelFactory = adapter.getClass().getDeclaredMethod("getModelFactory", HandlerMethod.class, WebDataBinderFactory.class);
        getModelFactory.setAccessible(true);


        ServletRequestDataBinderFactory factory = new ServletRequestDataBinderFactory(null, null);
        ModelFactory modelFactory = (ModelFactory)getModelFactory.invoke(adapter, handlerMethod, factory);
        ModelAndViewContainer container = new ModelAndViewContainer();

        //准备一个请求
        MockHttpServletRequest request = new MockHttpServletRequest();
        //request.setParameter("name", "yt");
        //初始化模型数据，也就是执行收集到的@ModelAttribute方法，将返回值放入ModelAndViewContainer
        modelFactory.initModel(new ServletWebRequest(request), container, handlerMethod);

        //调用控制器方法，发现如果请求参数有合适的数据进行参数绑定会优先使用请求参数，没有合适的则从容器中找一个同名的model参数赋值给控制器方法参数
        handlerMethod.invokeAndHandle(new ServletWebRequest(request), container, null);
        /**
         * 打印所有模型数据，来自两部分：
         *  RequestMappingHandlerAdapter初始化时解析@ModelAttribute修饰的方法
         *  访问控制器方法时，@ModelAttribute修饰的属性
         * ModelAndViewContainer容器中一个名称的数据只能有一份，因为容器本质是map，而model的名称就是key
         */
        ModelMap model = container.getModel();
        System.out.println(model);


    }
}
